#include "DataStruct.fxh"
#include "States.fxh"
#include "commonFunc.fxh"

float4x4 mWorld;                  // World matrix for object
//float4x4 mWorldView;              // Wold * View matrix
float4x4 mViewProjection;    // World * View * Projection matrix
float4   vAmbientColor;		 //abmient
float4   vDiffuseColor;      //diffuse
float4   vSpecularColor;     //Specular
float    fSpecularPower;     //Specular Power
Texture2D texDiffuse;         //Diffuse texture for mesh
bool     bTexture;            //boolean for using texture or not

//common variables
float3 eyePoint; //eye point vector
float fExpo; //exposure parameter
float fGamma; //gamma parameter

//EM textures
TextureCube	EMTex;

//SH lighting
float4 cAr;
float4 cAg;
float4 cAb;
float4 cBr;
float4 cBg;
float4 cBb;
float4 cC;

//random samples
static const int iNumSamples = 100;       //number of random samples
float4 smplsVecArray[iNumSamples];		///sample directions
float  fs[iNumSamples];			///normalization*BRDF/PDF

//ward brdf
float2 alpha;

//lafortune brdf
float4 lobe;


			 
		
//--------------------------------------------------------------------------------------
// This shader computes standard transform and lighting
//--------------------------------------------------------------------------------------
VS_OUTPUT_POSNORMTEXTANG_SHDIFFUSE RenderBRDFModelsVS( VS_INPUT_VERTNORMTEXTANG inVert )
{
    VS_OUTPUT_POSNORMTEXTANG_SHDIFFUSE Output;
	     
    // Transform the position from object space to homogeneous projection space
	// Be careful!!! use float4 although the input is float3!!!
	Output.PosWorld = mul(float4(inVert.Pos,1.0), mWorld).xyz;
	Output.Position = mul(float4(Output.PosWorld,1.0), mViewProjection);
	Output.TextureUV = inVert.TextureUV;
	Output.NormWorld = normalize(mul(inVert.Normal,(float3x3)mWorld));
	Output.TangWorld = normalize(mul(inVert.Tangent, (float3x3)mWorld));
	Output.BiNormWorld = normalize(mul(inVert.Binormal, (float3x3)mWorld));   
   
	//sh diffuse
	float4 vNormal = float4(inVert.Normal,1.0f);
    float3 x1, x2, x3;    
    // Linear + constant polynomial terms
    x1.r = dot(cAr,vNormal);
    x1.g = dot(cAg,vNormal);
    x1.b = dot(cAb,vNormal);
    
    // 4 of the quadratic polynomials
    float4 vB = vNormal.xyzz * vNormal.yzzx;   
    x2.r = dot(cBr,vB);
    x2.g = dot(cBg,vB);
    x2.b = dot(cBb,vB);
   
    // Final quadratic polynomial
    float vC = vNormal.x*vNormal.x - vNormal.y*vNormal.y;
    x3 = cC.rgb * vC;    

    Output.Diffuse.rgb = x1 + x2 + x3;
    Output.Diffuse.a   = 1.0f; 
    
    return Output;
}

float4 RenderBRDFModelsPS( VS_OUTPUT_POSNORMTEXTANG_SHDIFFUSE In ):SV_TARGET
{
	float4 color =  In.Diffuse*vDiffuseColor;
	return float4(SimpleTMandGamma(color.xyz,fExpo,fGamma),1);
}


//--------------------------------------------------------------------------------------
// Renders scene 
//--------------------------------------------------------------------------------------
technique11 RenderBRDFModels
{
    pass P0
    {        
		SetRasterizerState(RenderMesh_RS);
		SetDepthStencilState(EnableDepthTest,0);
        SetVertexShader(CompileShader(vs_5_0, RenderBRDFModelsVS()));
		SetGeometryShader( NULL );
        SetPixelShader(CompileShader(ps_5_0, RenderBRDFModelsPS())); 
    }
}

float4 RenderWardBRDFPS( VS_OUTPUT_POSNORMTEXTANG_SHDIFFUSE In ):SV_TARGET
{
	float3x3 LocalToWorld = float3x3(In.TangWorld, In.BiNormWorld, In.NormWorld);

	// construct the appropriate matrices	
	//http://jerome.jouvie.free.fr/opengl-tutorials/Lesson8.php
	float3x3 WorldToLocal;
	WorldToLocal._m00_m10_m20 = LocalToWorld._m00_m01_m02;
	WorldToLocal._m01_m11_m21 = LocalToWorld._m10_m11_m12;
	WorldToLocal._m02_m12_m22 = LocalToWorld._m20_m21_m22;
	
	// viewing direction in local coords
	float3 v = mul(eyePoint-In.PosWorld, WorldToLocal);
	v.z = max(v.z,0.02);
	v = normalize(v);	

	float4 c = float4(0,0,0,0);
	for (int i=0; i < iNumSamples; i++) 
	{
		// see genWardSamples in sequencegen.cpp for how samples are pre-calculated
		float dot_hv = dot(smplsVecArray[i].xyz, v);
		float3 u = (2.0 * dot_hv) * smplsVecArray[i].xyz - v;
		float3 u_w = mul(u, LocalToWorld);

		float f = dot_hv * fs[i] * sqrt(u.z/v.z);

		// Level-of-detail approximation:
		// If we assume the dual-paraboloid has no distortion, we can futher simplify
		// the LOD computation as being the resolution of the map (I), divided by the spherical 
		// area of the dual paraboloid map (2Pi), divided by the number of samples (N)
		//
		// Log[2, I*I/N/(2Pi)]*0.5 + 1 = Log[2, 512*512/40/(2*Pi)]*0.5 + 1 = 5.01329 + 1
		//float lod = 6.01329f - log2(smplsVecArray[i].w/dot_hv)*0.5;

		// conditional if to ensure that the sample is contributing a value to the integral;
		// this can be commented out for faster code at the cost of inaccuracy around
		// viewing directions tangent to the surface
		//if (u.z > 0) {
		//	c.rgb += getSample(front,back, float4(u_w, lod))*f;
		//	c.a++;
		//}
		if (u.z > 0) {
			//c.rgb +=EMTex.SampleLevel(LinearSampler,u_w,0).xyz*f;
			c.rgb +=EMTex.SampleLevel(LinearSampler,u_w,4).xyz*f;
			//c.rgb +=EMTex.Sample(LinearSampler,u_w).xyz*f;
			c.a++;
		}
	}

	c.rgb = c.rgb/c.a*vSpecularColor.xyz + In.Diffuse.xyz*vDiffuseColor.xyz;
	return float4(SimpleTMandGamma(c.xyz,fExpo,fGamma),1);
}


//--------------------------------------------------------------------------------------
// Renders scene 
//--------------------------------------------------------------------------------------
technique11 RenderWardBRDF
{
    pass P0
    {        
		SetRasterizerState(RenderMesh_RS);
		SetDepthStencilState(EnableDepthTest,0);
        SetVertexShader(CompileShader(vs_5_0, RenderBRDFModelsVS()));
		SetGeometryShader( NULL );
        SetPixelShader(CompileShader(ps_5_0, RenderWardBRDFPS())); 
    }
}


float4 RenderLafortuneBRDFPS( VS_OUTPUT_POSNORMTEXTANG_SHDIFFUSE In ):SV_TARGET
{
	// in reality, these need to be normalized, but that is excluded for efficiency
   float3x3 LocalToWorld = float3x3(In.TangWorld, In.BiNormWorld, In.NormWorld);

	// construct the appropriate matrices	
    //http://jerome.jouvie.free.fr/opengl-tutorials/Lesson8.php
	float3x3 WorldToLocal;
	WorldToLocal._m00_m10_m20 = LocalToWorld._m00_m01_m02;
	WorldToLocal._m01_m11_m21 = LocalToWorld._m10_m11_m12;
	WorldToLocal._m02_m12_m22 = LocalToWorld._m20_m21_m22;
	
	float3 eyeToVert = eyePoint-In.PosWorld;
	float3 v = normalize(mul(eyeToVert,WorldToLocal));
	v.z = max(v.z,0.01);	// avoid precision issue
	v = normalize(v);
	float3 r = lobe.xyz*v;
	float magnitude = pow(length(r),lobe.w);
	
	float3x3 SampleToLocal = constructCoordFrame(lobe, r);
	float3x3 SampleToWorld = mul(SampleToLocal, LocalToWorld);
	
	float4 c=0;
	for (int i=0; i < iNumSamples; i++) {
		// see genLafortuneSamples in sequencegen.cpp for how samples are pre-calculated
		float  f = max(0,dot(SampleToLocal._m20_m21_m22, smplsVecArray[i].xyz));
		float3 u_w  = mul(smplsVecArray[i].xyz, SampleToWorld);
		
		f *= magnitude*fs[i];

		// conditional if to ensure that the sample is contributing a value to the integral;
		// this can be commented out for faster code at the cost of inaccuracy around
		// viewing directions tangent to the surface
		//if (f > 0) {
		//	c.rgb += getSample(front,back,float4(u_w,smpls[i].w))*f;
		//	c.a++;
		//}
		if (f > 0) {
			//c.rgb +=EMTex.SampleLevel(LinearSampler,u_w,0).xyz*f;
			c.rgb +=EMTex.SampleLevel(LinearSampler,u_w,4).xyz*f;
			c.a++;
		}

	}

	float3 res = c.rgb/max(c.a,1);
	//float3 res = c.rgb/iNumSamples;  ///< Use this if commenting out the if-condition
	res = res*vSpecularColor.xyz+In.Diffuse.xyz*vDiffuseColor.xyz;
	return float4(SimpleTMandGamma(res.xyz,fExpo,fGamma),1);
	
}

//--------------------------------------------------------------------------------------
// Renders scene 
//--------------------------------------------------------------------------------------
technique11 RenderLafortuneBRDF
{
    pass P0
    {        
		SetRasterizerState(RenderMesh_RS);
		SetDepthStencilState(EnableDepthTest,0);
        SetVertexShader(CompileShader(vs_5_0, RenderBRDFModelsVS()));
		SetGeometryShader( NULL );
        SetPixelShader(CompileShader(ps_5_0, RenderLafortuneBRDFPS())); 
    }
}


VS_OUTPUT_POSNORMTEX RenderMirrorBallVS( VS_INPUT_VERTNORMTEXTANG inVert )
{
    VS_OUTPUT_POSNORMTEX Output;
	     
    // Transform the position from object space to homogeneous projection space
	// Be careful!!! use float4 although the input is float3!!!
	Output.PosWorld = mul(float4(inVert.Pos,1.0), mWorld).xyz;
	Output.Position = mul(float4(Output.PosWorld,1.0), mViewProjection);
	Output.TextureUV = inVert.TextureUV;
	Output.NormWorld = normalize(mul(inVert.Normal,(float3x3)mWorld));
	return Output;
}

float4 RenderMirrorBallPS( VS_OUTPUT_POSNORMTEX In ):SV_TARGET
{
	float3 normal = normalize(In.NormWorld);
	float3 v = normalize(eyePoint-In.PosWorld);
	float3 vReflect = normalize(reflect(-v,normal)); //careful! HLSL reflect function is inverse!
	float3 color = EMTex.SampleLevel(LinearSampler,vReflect,0).xyz;

	return float4(SimpleTMandGamma(color,fExpo,fGamma),1);
}

//--------------------------------------------------------------------------------------
// Renders scene 
//--------------------------------------------------------------------------------------
technique11 RenderMirrorBall
{
    pass P0
    {        
		SetRasterizerState(RenderMesh_RS);
		SetDepthStencilState(EnableDepthTest,0);
        SetVertexShader(CompileShader(vs_5_0, RenderMirrorBallVS()));
		SetGeometryShader( NULL );
        SetPixelShader(CompileShader(ps_5_0, RenderMirrorBallPS())); 
    }
}